package com.deng.eventflow.core.util;


import com.deng.eventflow.core.Context;
import com.deng.eventflow.core.EventTracker;
import com.deng.eventflow.core.functions.BinaryFunction;
import com.deng.eventflow.core.functions.UnaryFunction;

import java.util.Collection;

public class Booleans {

    private Booleans() {
        // Utility class
    }

    public static final transient And and = And.instance;

    public static final transient Or or = Or.instance;

    public static final transient Implies implies = Implies.instance;

    public static final transient Not not = Not.instance;

    public static final transient BagAnd bagAnd = BagAnd.instance;

    public static final transient BagOr bagOr = BagOr.instance;

    public static class And extends BinaryFunction<Boolean, Boolean, Boolean>
    {
        public static final transient And instance = new And();

        private And()
        {
            super(Boolean.class, Boolean.class, Boolean.class);
        }

        @Override
        public Boolean getValue(Boolean x, Boolean y)
        {
            return x.booleanValue() && y.booleanValue();
        }

        @Override
        protected void trackAssociations(Boolean x, Boolean y, Boolean z, EventTracker tracker)
        {
            if (!x)
            {
                tracker.associateToOutput(-1, 0, 0, 0, 0);
            }
            else if (!y)
            {
                tracker.associateToOutput(-1, 1, 0, 0, 0);
            }
            else
            {
                tracker.associateToOutput(-1, 0, 0, 0, 0);
                tracker.associateToOutput(-1, 1, 0, 0, 0);
            }
        }

        @Override
        public boolean evaluatePartial(Object[] inputs, Object[] outputs, Context context)
        {
            if (inputs[0] != null && ((Boolean) inputs[0]) == false)
            {
                outputs[0] = false;
                return true;
            }
            if (inputs[1] != null && ((Boolean) inputs[1]) == false)
            {
                outputs[0] = false;
                return true;
            }
            if (inputs[0] != null && inputs[1] != null)
            {
                outputs[0] = ((Boolean) inputs[0]) && ((Boolean) inputs[1]);
                return true;
            }
            return false;
        }

        @Override
        public String toString()
        {
            return "∧";
        }
    }

    public static class Implies extends BinaryFunction<Boolean, Boolean, Boolean>
    {
        public static final transient Implies instance = new Implies();

        private Implies()
        {
            super(Boolean.class, Boolean.class, Boolean.class);
        }

        @Override
        public Boolean getValue(Boolean x, Boolean y)
        {
            return !x.booleanValue() || y.booleanValue();
        }

        @Override
        public boolean evaluatePartial(Object[] inputs, Object[] outputs, Context context)
        {
            if (inputs[0] != null && ((Boolean) inputs[0]) == false)
            {
                outputs[0] = true;
                return true;
            }
            if (inputs[1] != null && ((Boolean) inputs[1]) == true)
            {
                outputs[0] = true;
                return true;
            }
            if (inputs[0] != null && inputs[1] != null)
            {
                outputs[0] = !((Boolean) inputs[0]) || ((Boolean) inputs[1]);
                return true;
            }
            return false;
        }

        @Override
        protected void trackAssociations(Boolean x, Boolean y, Boolean z, EventTracker tracker)
        {
            if (!x)
            {
                tracker.associateToOutput(-1, 0, 0, 0, 0);
            }
            else if (y)
            {
                tracker.associateToOutput(-1, 1, 0, 0, 0);
            }
            else
            {
                tracker.associateToOutput(-1, 0, 0, 0, 0);
                tracker.associateToOutput(-1, 1, 0, 0, 0);
            }
        }

        @Override
        public String toString()
        {
            return "→";
        }
    }

    public static class Or extends BinaryFunction<Boolean, Boolean, Boolean>
    {
        public static final transient Or instance = new Or();

        private Or()
        {
            super(Boolean.class, Boolean.class, Boolean.class);
        }

        @Override
        public Boolean getValue(Boolean x, Boolean y)
        {
            return x.booleanValue() || y.booleanValue();
        }

        @Override
        protected void trackAssociations(Boolean x, Boolean y, Boolean z, EventTracker tracker)
        {
            if (x)
            {
                tracker.associateToOutput(-1, 0, 0, 0, 0);
            }
            else if (y)
            {
                tracker.associateToOutput(-1, 1, 0, 0, 0);
            }
            else
            {
                tracker.associateToOutput(-1, 0, 0, 0, 0);
                tracker.associateToOutput(-1, 1, 0, 0, 0);
            }
        }

        @Override
        public boolean evaluatePartial(Object[] inputs, Object[] outputs, Context context)
        {
            if (inputs[0] != null && ((Boolean) inputs[0]) == true)
            {
                outputs[0] = true;
                return true;
            }
            if (inputs[1] != null && ((Boolean) inputs[1]) == true)
            {
                outputs[0] = true;
                return true;
            }
            if (inputs[0] != null && inputs[1] != null)
            {
                outputs[0] = ((Boolean) inputs[0]) || ((Boolean) inputs[1]);
                return true;
            }
            return false;
        }

        @Override
        public String toString()
        {
            return "∨";
        }
    }

    public static class Not extends UnaryFunction<Boolean, Boolean>
    {
        public static final transient Not instance = new Not();

        private Not()
        {
            super(Boolean.class, Boolean.class);
        }

        @Override
        public Boolean getValue(Boolean x)
        {
            return !x.booleanValue();
        }

        @Override
        public String toString()
        {
            return "¬";
        }
    }

    public static class BagAnd extends UnaryFunction<Object,Boolean>
    {
        public static final transient BagAnd instance = new BagAnd();

        protected BagAnd()
        {
            super(Object.class, Boolean.class);
        }

        @Override
        public Boolean getValue(Object o)
        {
            if (o.getClass().isArray())
            {
                Object[] a = (Object[]) o;
                for (Object e : a)
                {
                    if (!parseBoolValue(e))
                    {
                        return false;
                    }
                }
                return true;
            }
            if (o instanceof Collection)
            {
                Collection<?> a = (Collection<?>) o;
                for (Object e : a)
                {
                    if (!parseBoolValue(e))
                    {
                        return false;
                    }
                }
                return true;
            }
            return false;
        }
    }

    public static class BagOr extends UnaryFunction<Object,Boolean>
    {
        public static final transient BagOr instance = new BagOr();

        protected BagOr()
        {
            super(Object.class, Boolean.class);
        }

        @Override
        public Boolean getValue(Object o)
        {
            if (o.getClass().isArray())
            {
                Object[] a = (Object[]) o;
                for (Object e : a)
                {
                    if (parseBoolValue(e))
                    {
                        return true;
                    }
                }
                return false;
            }
            if (o instanceof Collection)
            {
                Collection<?> a = (Collection<?>) o;
                for (Object e : a)
                {
                    if (parseBoolValue(e))
                    {
                        return true;
                    }
                }
                return false;
            }
            return true;
        }
    }

    public static boolean parseBoolValue(Object o)
    {
        if (o instanceof Boolean)
        {
            return (Boolean) o;
        }
        else if (o instanceof String)
        {
            String s = (String) o;
            return s.compareToIgnoreCase("true") == 0 || s.compareToIgnoreCase("T") == 0
                    || s.compareToIgnoreCase("1") == 0;
        }
        if (o instanceof Number)
        {
            Number n = (Number) o;
            return Math.abs(n.doubleValue()) >= 0.00001;
        }
        // When in doubt, return false
        return false;
    }

}
